package com.hk.webapp.utils;

import com.hk.webapp.res_config.JwtAudience;
import com.hk.webapp.bo.UserBo;
import com.hk.webapp.error.BusinessErrorEnum;
import com.hk.webapp.error.BusinessException;
import com.hk.webapp.utils.encryptions.EncryptUtil;
import io.jsonwebtoken.*;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.util.*;

/**
 * (C) Copyright 2017-now
 * All rights reserved.
 * <p>
 * jwt组件
 *
 * @author DreamerCK
 * @date 2019-01-08 17:21
 **/
@Slf4j
@Component
public class JwtUtil {

    private static final String TOKEN_HEADER_TYPE = "hk-JWT";
    private static final String TOKEN_PREFIX_REGEX = "^[B|b][E|e][A|a][R|r][E|e][R|r] .*";
    private static final String AUTH_HEADER = "Authorization";
    private static final String USER_ID = "userId";
    private static final String USERNAME = "username";
    private static final SignatureAlgorithm SIGNATURE_ALGORITHM = SignatureAlgorithm.HS256;

    public static String generateToken(JwtAudience audience, Integer userId, String username, boolean isAdmin) throws BusinessException {
        String secretKey = isAdmin ? audience.getAdminSecretKey() : audience.getClientSecretKey();
        try {
            String encryptUserId = EncryptUtil.encrypt(userId.toString());
            JwtBuilder builder = Jwts.builder()
                    .setHeaderParam("typ", TOKEN_HEADER_TYPE)
                    .setIssuer(audience.getIssuer())
                    .setId(encryptUserId)
                    .setSubject(username)
                    .setExpiration(generateExpirationDate(audience))
                    .setIssuedAt(new DateTime().toDate())
                    .signWith(SIGNATURE_ALGORITHM, secretKey);
            return builder.compact();
        } catch (Exception e) {
            log.warn("【JWT TOKEN】token 创建失败,userId:{},username:{}", userId, username);
            throw new BusinessException(BusinessErrorEnum.JWT_CREATE_ERROR);
        }
    }

    public static UserBo validateTokenAndAddUsernameToHeader(HttpServletRequest request, String secretKey) throws BusinessException {
        //校验token信息
        String token = validateToken(request);
        // parse the token.
        try {
            UserBo userBo = getUsernameFromToken(token, secretKey);
            if (Objects.isNull(userBo)) {
                throw new BusinessException(BusinessErrorEnum.JWT_TOKEN_INVALID);
            }
            return userBo;
        } catch (ExpiredJwtException jwtExpired) {
            throw new BusinessException(BusinessErrorEnum.JWT_TOKEN_EXPIRES);
        }
    }

    public static String refreshToken(HttpServletRequest request, JwtAudience audience, boolean isAdmin) throws Exception {
        String refreshedToken;
        //校验token信息
        String token = validateToken(request);
        String secretKey = isAdmin ? audience.getAdminSecretKey() : audience.getClientSecretKey();
        try {
            final Claims claims = getClaimsFromToken(token, secretKey);
            if (Objects.isNull(claims)) {
                throw new BusinessException(BusinessErrorEnum.JWT_TOKEN_INVALID);
            }
            claims.setIssuedAt(new Date());
            claims.setExpiration(generateExpirationDate(audience));
            refreshedToken = generateToken(claims, secretKey);
        } catch (ExpiredJwtException jwtExpired) {
            final Claims claims = jwtExpired.getClaims();
            claims.setIssuedAt(new Date());
            claims.setExpiration(generateExpirationDate(audience));
            refreshedToken = generateToken(claims, secretKey);
        }
        return refreshedToken;
    }


    private static String validateToken(HttpServletRequest request) throws BusinessException {
        String authHeader = request.getHeader(AUTH_HEADER);
        if (StringUtils.isBlank(authHeader)) {
            log.info("【JWT TOKEN】token为空,需要登录,token:{}", authHeader);
            throw new BusinessException(BusinessErrorEnum.LL_NEED_LOGIN);
        }
        if (!authHeader.matches(TOKEN_PREFIX_REGEX)) {
            log.info("【JWT TOKEN】token 缺少bearer前缀信息");
            throw new BusinessException(BusinessErrorEnum.JWT_TOKEN_INVALID);
        }
        return extractJwtTokenFromAuthorizationHeader(authHeader);
    }

    private static String generateToken(Claims claims, String secretKey) throws BusinessException {
        try {
            JwtBuilder builder = Jwts.builder()
                    .setHeaderParam("typ", TOKEN_HEADER_TYPE)
                    .setClaims(claims)
                    .signWith(SIGNATURE_ALGORITHM, secretKey);
            return builder.compact();
        } catch (Exception e) {
            throw new BusinessException(BusinessErrorEnum.JWT_CREATE_ERROR);
        }
    }

    private static UserBo getUsernameFromToken(String token, String secretKey) {
        final Claims claims = getClaimsFromToken(token, secretKey);
        if (Objects.isNull(claims)) {
            return null;
        }
        String userIdStr = claims.getId();
        if (StringUtils.isBlank(userIdStr)) {
            return null;
        }
        try {
            userIdStr = EncryptUtil.decrypt(userIdStr);
        } catch (Exception e) {
            return null;
        }
        Integer userId = Integer.valueOf(userIdStr);
        String username = claims.getSubject();
        return UserBo.builder().userId(userId).username(username).build();
    }

    private static Claims getClaimsFromToken(String token, String secretKey) {
        Claims claims;
        try {
            claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody();
        } catch (ExpiredJwtException jwtExpired) {
            throw jwtExpired;
        } catch (Exception e) {
            e.printStackTrace();
            claims = null;
        }
        return claims;
    }

    public static class CustomHttpServletRequest extends HttpServletRequestWrapper {
        @Setter
        private String username;
        @Setter
        @Getter
        private String userId;

        private CustomHttpServletRequest(HttpServletRequest request, UserBo userBo) {
            super(request);
            this.userId = userBo.getUserId().toString();
            this.username = userBo.getUsername();
        }

        public CustomHttpServletRequest(HttpServletRequest request) {
            super(request);
        }

        @Override
        public Enumeration<String> getHeaders(String name) {
            if (StringUtils.isNotBlank(name)) {
                if (name.equals(USERNAME)) {
                    return Collections.enumeration(Collections.singletonList(username));
                } else if (name.equals(USER_ID)) {
                    return Collections.enumeration(Collections.singletonList(userId));
                }
            }
            return super.getHeaders(name);
        }
    }

    private static Date generateExpirationDate(JwtAudience audience) {
        DateTime dateTime = new DateTime();
        DateTime date = dateTime.plusHours(audience.getExpires());
        return date.toDate();
    }

    private static String extractJwtTokenFromAuthorizationHeader(String auth) {
        //Replacing "Bearer Token" to "Token" directly
        return auth.replaceFirst("[B|b][E|e][A|a][R|r][E|e][R|r] ", "").replace(" ", "");
    }

}
